package org.csploit.android.net.datasource;

import org.csploit.android.core.Logger;
import org.csploit.android.core.System;
import org.csploit.android.net.RemoteReader;
import org.csploit.android.net.Target;
import org.csploit.android.net.reference.CVE;
import org.csploit.android.net.reference.OSVDB;
import org.unbescape.html.HtmlEscape;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class ExploitDb
{
  public static class Exploit extends Target.Exploit {

    public Exploit(int id, String summary) {
      this.name = summary;
      this.summary = summary;
      this.url = "http://www.exploit-db.com/exploits/" + id;
      this.id = "EDB-" + id;
    }
  }

  private static class ExploitReceiver implements RemoteReader.Receiver {
    private final static Pattern CVEID = Pattern.compile("CVE-([0-9]+-[0-9]+)");
    private final static Pattern OSVDBID = Pattern.compile("osvdb\\.org/show/osvdb/([0-9]+)");

    private final RemoteReader.Job job;
    private final Exploit exploit;
    private final Search.Receiver<Target.Exploit> changeReceiver;

    public ExploitReceiver(RemoteReader.Job job, Exploit exploit, Search.Receiver<Target.Exploit> changeReceiver) {
      this.job = job;
      this.exploit = exploit;
      this.changeReceiver = changeReceiver;
    }

    @Override
    public void onContentFetched(byte[] content) {
      Matcher matcher;
      String html = new String(content);

      matcher = CVEID.matcher(html);

      if(matcher.find()) {
        CVE cve = new CVE(new String(matcher.group(1).toCharArray()));
        exploit.addReference(cve);
        try {
          job.add(cve.getUrl(), new CVEDetails.Receiver(exploit, cve, changeReceiver));
        } catch (MalformedURLException e) {
          System.errorLogging(e);
        } catch (IllegalStateException e) {
          Logger.warning(e.getMessage());
        }
      }

      matcher = OSVDBID.matcher(html);

      if(matcher.find()) {
        OSVDB osvdb = new OSVDB(Integer.parseInt(matcher.group(1)));
        exploit.addReference(osvdb);
        // job.add(osvdb.getUrl(), new OSVDBReceiver(?)); // TODO: defeat cloudfare javascript checker
      }
    }

    @Override
    public void onError(byte[] description) {
      Logger.error(String.format("%s: %s", exploit.getUrl(), new String(description)));
    }
  }

  private static class ResultsReceiver implements RemoteReader.Receiver {

    private final static Pattern RESULT = Pattern.compile("(http://www\\.exploit-db\\.com/exploits/([0-9]+)/)\" *>([^<]+)</a");
    private final static Pattern PAGES = Pattern.compile("filter_page=([0-9]+)");

    private final Search.Receiver<Target.Exploit> receiver;
    private final RemoteReader.Job job;
    private final Target.Port port;

    private String originalQuery;
    private boolean analyzePagination = true;

    public ResultsReceiver(RemoteReader.Job job, String query, Target.Port port, Search.Receiver<Target.Exploit> receiver) {
      this.originalQuery = query;
      this.receiver = receiver;
      this.job = job;
      this.port = port;
    }

    public ResultsReceiver(RemoteReader.Job job, String query, Search.Receiver<Target.Exploit> receiver) {
      this(job, query, null, receiver);
    }

    private void parsePages(String body) {
      Matcher matcher = PAGES.matcher(body);
      ArrayList<Integer> pages = new ArrayList<Integer>(10);

      while(matcher.find()) {
        Integer i = Integer.parseInt(matcher.group(1));
        if(!pages.contains(i))
          pages.add(i);
      }

      Collections.sort(pages);

      for(int p : pages) {
        try {
          job.add(originalQuery + "&filter_page=" + p, this);
        } catch (MalformedURLException e) {
          Logger.error(String.format("%s&filter_page=%d: Bad URL", originalQuery, p));
        }
      }
    }

    @Override
    public void onContentFetched(byte[] content) {
      String body = new String(content);
      Matcher matcher = RESULT.matcher(body);

      while(matcher.find()) {
        Exploit exploit = new Exploit(Integer.parseInt(matcher.group(2)),
                HtmlEscape.unescapeHtml(matcher.group(3)));

        exploit.setPort(port);

        try {
          job.add(new String(matcher.group(1).toCharArray()),
                  new ExploitReceiver(job, exploit, receiver));
        } catch (MalformedURLException e) {
          Logger.error(String.format("Bad URL: %s", matcher.group(1)));
        } catch (IllegalStateException e) {
          Logger.warning(e.getMessage());
        }

        receiver.onItemFound(exploit);
      }

      synchronized (this) {
        if (!analyzePagination)
          return;
        analyzePagination = false;
      }

      parsePages(body);
    }

    @Override
    public void onError(byte[] description) {
      Logger.error(new String(description));
    }
  }

  public void beginSearch(RemoteReader.Job job, String query, Target.Port port, Search.Receiver<Target.Exploit> receiver) {
    ResultsReceiver r;
    String url;

    url = "http://www.exploit-db.com/search/?action=search&filter_description=";

    try {
      url += URLEncoder.encode(query, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      url += URLEncoder.encode(query);
    }

    Logger.debug(String.format("url = '%s'", url));

    r = new ResultsReceiver(job, url, port, receiver);
    try {
      job.add(url, r);
    } catch (MalformedURLException e) {
      Logger.error("Bad URL: " + url);
    } catch (IllegalStateException e) {
      System.errorLogging(e);
    }
  }

  public void beginSearch(RemoteReader.Job job, String query, Search.Receiver<Target.Exploit> receiver) {
    beginSearch(job, query, null, receiver);
  }
}